package org.bluedream.comm.utils;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey;
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Scanner;

public class EncryForRSAUtil {
    private EncryForRSAUtil() {
        throw new AssertionError();
    }
    /**
     * 获取公钥的key
     */
    public static final String PUBLIC_KEY = "RSAPublicKey";

    /**
     * 获取私钥的key
     */
    public static final String PRIVATE_KEY = "RSAPrivateKey";

    /**
     * 加密算法RSA
     */
    public static final String KEY_ALGORITHM = "RSA";

    public static final String KEY_PROVIDER = "BC";
    /**
     * RSA 位数 如果采用2048 上面最大加密和最大解密则须填写:  245 256
     */
    private static final int INITIALIZE_LENGTH = 1024;

    /**
     * 签名算法
     */
    public static final String SIGNATURE_ALGORITHM = "MD5withRSA";

    private static String digits = "0123456789abcdef";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private static KeyFactory getKeyFactory() throws NoSuchProviderException, NoSuchAlgorithmException {
        return KeyFactory.getInstance("RSA", "BC");
    }

    private static KeyPairGenerator keyPairGenerator() throws NoSuchProviderException, NoSuchAlgorithmException {
        // KeyPairGenerator类用于生成公钥和私钥对，基于RSA算法生成对象
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM , KEY_PROVIDER);
        // 初始化密钥对生成器，密钥大小为96-1024位
        keyPairGen.initialize(1024);
        return keyPairGen;
    }

    /**
     * 随机生成密钥对
     * @return HashMap<String , Object>
     * @throws NoSuchAlgorithmException
     */
    public static HashMap<String , Object> genKeyPair() throws Exception {
        HashMap<String , Object> map = new HashMap<>();
        // 生成一个密钥对，保存在keyPair中
        KeyPair keyPair = keyPairGenerator().generateKeyPair();
        //得到公钥
        RSAPublicKey publicKey = (RSAPublicKey)keyPair.getPublic();
        //得到私钥
        RSAPrivateKey privateKey = (RSAPrivateKey)keyPair.getPrivate();
        map.put(PUBLIC_KEY , publicKey);
        map.put(PRIVATE_KEY , privateKey);
        return map;
    }

    /**
     * 使用私钥对生成数字签名
     * @param encryptData  已加密的数据
     * @param key          私钥****
     * @return
     * @throws Exception
     */
    public static String sign(String encryptData, String key) throws Exception {
        byte[] bInput = encryptData.getBytes("UTF8");
        RSAPrivateKey privateKey = getPrivateKey(key);
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(privateKey);
        signature.update(bInput);
        return EncryptUtil.encodeBase64String(signature.sign());
    }

    /**
     * 校验数字签名
     * @param encryptData   已加密的数据
     * @param key           公钥****
     * @param sign          数字签名
     * @return
     * @throws Exception
     */
    public static boolean verify(String encryptData, String key, String sign) throws Exception {
        byte[] bInput = encryptData.getBytes("UTF8");
        RSAPublicKey publicKey = getPublicKey(key);
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initVerify(publicKey);
        signature.update(bInput);
        return signature.verify(EncryptUtil.decodeBase64Byte(sign));
    }

    /**
     * 通过公钥byte[](publicKey.getEncoded())将公钥还原，适用于RSA算法
     * @param keyEncode
     * @return
     * @throws Exception
     */
    public static RSAPublicKey getPublicKey(byte[] keyEncode) throws Exception {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyEncode);
        return (RSAPublicKey)getKeyFactory().generatePublic(keySpec);
    }

    /**
     * 使用模和指数生成RSA公钥
     * 注意：【此代码用了默认补位方式，为RSA/None/PKCS1Padding，不同JDK默认的补位方式可能不同，如Android默认是RSA/None/NoPadding】
     * @param argM 模    N
     * @param argE 指数  E
     * @return
     */
    public static RSAPublicKey getPublicKey(String argM , String argE) throws Exception{
        BigInteger bigM = new BigInteger(argM);
        BigInteger bigE = new BigInteger(argE);
        RSAPublicKeySpec spec = new RSAPublicKeySpec(bigM , bigE);
        return (RSAPublicKey)getKeyFactory().generatePublic(spec);
    }

    /**
     * 从Map中  获取公钥
     * @param keyMap
     * @return
     */
    public static String getPublicKey(HashMap<String, Object> keyMap) {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return EncryptUtil.encodeBase64String(key.getEncoded());
    }

    /**
     * 返回公钥的字符串
     * @param key
     * @return
     */
    public static String getPublicKey(RSAPublicKey key){
        return EncryptUtil.encodeBase64String(key.getEncoded());
    }

    /**
     * 通过公钥String 将公钥还原，适用于RSA算法
     * @param key
     * @return
     * @throws Exception
     */
    public static RSAPublicKey getPublicKey(String key) throws Exception{
        byte[] keyBytes = EncryptUtil.decodeBase64Byte(key);
        return getPublicKey(keyBytes);
    }

    /**
     * 通过私钥byte[](privateKey.getEncoded())将私钥还原，适用于RSA算法
     * @param keyEncode
     * @return
     * @throws Exception
     */
    public static RSAPrivateKey getPrivateKey(byte[] keyEncode) throws Exception {
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyEncode);
        return (RSAPrivateKey)getKeyFactory().generatePrivate(keySpec);
    }

    /**
     * 使用模和指数生成RSA私钥
     * 注意：【此代码用了默认补位方式，为RSA/None/PKCS1Padding，不同JDK默认的补位方式可能不同，如Android默认是RSA/None/NoPadding】
     * @param argM 模     N
     * @param argE 指数   E
     * @return
     * @throws Exception
     */
    public static RSAPrivateKey getPrivateKey(String argM , String argE) throws Exception {
        BigInteger bigM = new BigInteger(argM);
        BigInteger bigE = new BigInteger(argE);
        RSAPrivateKeySpec spec = new RSAPrivateKeySpec(bigM , bigE);
        return (RSAPrivateKey) getKeyFactory().generatePrivate(spec);
    }

    /**
     * 从Map中    获取私钥
     * @param keyMap
     * @return
     */
    public static String getPrivateKey(HashMap<String, Object> keyMap) {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return EncryptUtil.encodeBase64String(key.getEncoded());
    }

    /**
     * 返回私钥的字符串
     * @param key
     * @return
     */
    public static String getPrivateKey(RSAPrivateKey key){
        return EncryptUtil.encodeBase64String(key.getEncoded());
    }

    /**
     * 通过私钥String 将私钥还原，适用于RSA算法
     * @param key
     * @return
     * @throws Exception
     */
    public static RSAPrivateKey getPrivateKey(String key) throws Exception{
        byte[] keyBytes = EncryptUtil.decodeBase64Byte(key);
        return getPrivateKey(keyBytes);
    }

    /**
     * 密钥加密
     * @param data  明文数据
     * @param key   密钥
     * @return
     * @throws Exception
     */
    public static String encryptByRSAKey(String data , Object key) throws Exception {
        byte[] bInput = data.getBytes("UTF8");
        Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding", "BC");
        int CellLength = 0;
        if (key instanceof RSAPublicKey){
            RSAPublicKey var1 = (RSAPublicKey)key;
            cipher.init(Cipher.ENCRYPT_MODE, var1);
            CellLength = var1.getModulus().bitLength() / 8;
        }else if (key instanceof RSAPrivateKey){
            RSAPrivateKey var2 = (RSAPrivateKey)key;
            cipher.init(Cipher.ENCRYPT_MODE, var2);
            CellLength = var2.getModulus().bitLength() / 8;
        }else {
            throw new RuntimeException("无法加密，传入密钥无法识别！");
        }
        //模 长
        int MaxLength = CellLength - 11;
        int GroupLength = (int)Math.ceil((double)bInput.length * 1.0D / (double)MaxLength);
        byte[] result = new byte[GroupLength * CellLength];

        for(int i = 0; i < GroupLength; ++i) {
            int len = MaxLength < bInput.length - MaxLength * i ? MaxLength : bInput.length - MaxLength * i;
            byte[] inByte = new byte[len];
            System.arraycopy(bInput, MaxLength * i, inByte, 0, len);
            byte[] temp = cipher.doFinal(inByte);
            System.arraycopy(temp, 0, result, i * CellLength, CellLength);
        }
        return StringUtils.toHex(result , digits);
    }

    /**
     * 根据type[PUBLIC_KEY/PRIVATE_KEY]决定使用何种方式加密
     * @param data      明文数据
     * @param key       密钥字符串
     * @param type      密钥类型
     * @return
     * @throws Exception
     */
    public static String encryptByRSAKey(String data , String key , String type) throws Exception{
        if (type.equals(PUBLIC_KEY)){
            RSAPublicKey publicKey = getPublicKey(key);
            return encryptByRSAKey(data , publicKey);
        }else if (type.equals(PRIVATE_KEY)){
            RSAPrivateKey privateKey = getPrivateKey(key);
            return encryptByRSAKey(data , privateKey);
        }else {
            throw new RuntimeException("无法加密，密钥类型不正确");
        }
    }

    /**
     * 根据type[PUBLIC_KEY/PRIVATE_KEY]决定使用何种方式加密
     * @param data  明文数据
     * @param argM  模
     * @param argE  指数
     * @param type  密钥类型
     * @return
     * @throws Exception
     */
    public static String encryptByRSAKey(String data , String argM , String argE , String type) throws Exception {
        if (type.equals(PUBLIC_KEY)){
            RSAPublicKey publicKey = getPublicKey(argM , argE);
            return encryptByRSAKey(data , publicKey);
        }else if (type.equals(PRIVATE_KEY)){
            RSAPrivateKey privateKey = getPrivateKey(argM , argE);
            return encryptByRSAKey(data , privateKey);
        }else {
            throw new RuntimeException("无法加密，密钥类型不正确");
        }
    }

    /**
     * 解密
     * @param encryptData      加密数据
     * @param key              密钥
     * @return
     * @throws Exception
     */
    public static String decryByByRSAKey(String encryptData, Object key) throws Exception {
        byte[] bInput = StringUtils.hexStr2Bytes(encryptData);
        Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding", "BC");
        int CellLength = 0;
        if (key instanceof RSAPublicKey){
            RSAPublicKey var1 = (RSAPublicKey)key;
            cipher.init(Cipher.DECRYPT_MODE, var1);
            //模 长
            CellLength = var1.getModulus().bitLength() / 8;
        }else if (key instanceof RSAPrivateKey){
            RSAPrivateKey var2 = (RSAPrivateKey) key;
            cipher.init(Cipher.DECRYPT_MODE, var2);
            //模 长
            CellLength = var2.getModulus().bitLength() / 8;
        }else {
            throw new RuntimeException("无法解密，传入密钥无法识别！");
        }
        int MaxLength = CellLength - 11;
        int GroupLength = (int)Math.ceil((double)bInput.length * 1.0D / (double)CellLength);
        List<byte[]> plainList = new ArrayList();
        int len = 0;

        for(int i = 0; i < GroupLength; ++i) {
            byte[] inByte = new byte[CellLength];
            System.arraycopy(bInput, CellLength * i, inByte, 0, CellLength);
            byte[] temp = cipher.doFinal(inByte);
            plainList.add(temp);
            len += temp.length;
        }
        byte[] plainText = new byte[len];
        for(int i = 0; i < GroupLength; ++i) {
            System.arraycopy(plainList.get(i), 0, plainText, i * MaxLength, ((byte[])plainList.get(i)).length);
        }
        return byteToString(plainText);
    }

    /**
     * 根据type[PUBLIC_KEY/PRIVATE_KEY]决定使用何种方式解密
     * @param encryptData
     * @param key
     * @param type
     * @return
     * @throws Exception
     */
    public static String decryByByRSAKey(String encryptData, String key , String type) throws Exception{
        if (type.equals(PUBLIC_KEY)){
            RSAPublicKey publicKey = getPublicKey(key);
            return decryByByRSAKey(encryptData , publicKey);
        }else if (type.equals(PRIVATE_KEY)){
            RSAPrivateKey privateKey = getPrivateKey(key);
            return decryByByRSAKey(encryptData , privateKey);
        }else {
            throw new RuntimeException("无法解密，密钥类型不正确");
        }
    }

    /**
     * 根据type[PUBLIC_KEY/PRIVATE_KEY]决定使用何种方式解密
     * @param encryptData   密文
     * @param argM          模
     * @param argE          指数
     * @param type          密钥类型
     * @return
     * @throws Exception
     */
    public static String decryByByRSAKey(String encryptData , String argM , String argE , String type) throws Exception {
        if (type.equals(PUBLIC_KEY)){
            RSAPublicKey publicKey = getPublicKey(argM , argE);
            return decryByByRSAKey(encryptData , publicKey);
        }else if (type.equals(PRIVATE_KEY)){
            RSAPrivateKey privateKey = getPrivateKey(argM , argE);
            return decryByByRSAKey(encryptData , privateKey);
        }else {
            throw new RuntimeException("无法解密，密钥类型不正确");
        }
    }

    private static String byteToString(byte[] bytes) {
        return byteToString(bytes, bytes.length);
    }

    private static String byteToString(byte[] bytes, int length) {
        char[] chars = new char[length];

        for(int i = 0; i != chars.length; ++i) {
            chars[i] = (char)(bytes[i] & 255);
        }
        return new String(chars);
    }

    /**
     * Test
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        Process process = Runtime.getRuntime().exec(new String[] { "wmic", "cpu", "get", "ProcessorId" });

        process.getOutputStream().close();

        Scanner sc = new Scanner(process.getInputStream());

        String property = sc.next();

        String serial = sc.next();
        String regDate = "2023-01-01";
        String sign = "{\"processorid\":\"" + serial + "\",\"systemDate\":\"" + regDate + "\"}";


        HashMap<String , Object> map = EncryForRSAUtil.genKeyPair();

        String publicKey = EncryForRSAUtil.getPublicKey(map);
        String privateKey = EncryForRSAUtil.getPrivateKey(map);

        String cert = EncryForRSAUtil.encryptByRSAKey(sign , publicKey , EncryForRSAUtil.PUBLIC_KEY);

        System.out.println("设备码：" + serial);
        System.out.println("注册号：" + cert);
        System.out.println("key:" + privateKey);
        System.out.println("注册期限：" + regDate);

    }

}
